import sys
import json
import logging
import threading
import socket
import select
import ssl
import time
from urllib.parse import urlparse
from datetime import datetime
import requests
import upnpclient
import os
from cryptography import x509
from cryptography.x509.oid import NameOID
from cryptography.hazmat.primitives import hashes, serialization
from cryptography.hazmat.primitives.asymmetric import rsa
from cryptography.hazmat.backends import default_backend
import datetime
from PyQt5.QtWidgets import (QApplication, QMainWindow, QTabWidget, QVBoxLayout, 
                            QWidget, QLabel, QLineEdit, QPushButton, QTextEdit,
                            QHBoxLayout, QFormLayout, QComboBox, QCheckBox,
                            QGroupBox, QSpinBox, QMessageBox, QProgressBar)
from PyQt5.QtCore import QTimer, Qt, pyqtSignal, QObject
from flask import Flask, request, Response, abort
from werkzeug.datastructures import Headers

# 配置日志
logging.basicConfig(
    level=logging.INFO,
    format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
    handlers=[
        logging.FileHandler('tunnel.log'),
        logging.StreamHandler()
    ]
)
logger = logging.getLogger(__name__)

class ProtocolTypes:
    HTTP = "HTTP"
    HTTPS = "HTTPS"
    TCP = "TCP"
    UDP = "UDP"
    STCP = "STCP"
    XTCP = "XTCP"
    UPNP = "UPNP"

class TunnelSignals(QObject):
    log_message = pyqtSignal(str)
    status_update = pyqtSignal(str)
    proxy_added = pyqtSignal(dict)
    proxy_removed = pyqtSignal(str, str)  # protocol, key

class TunnelServer:
    def __init__(self, config_path='config.json'):
        self.app = Flask(__name__)
        self.signals = TunnelSignals()
        self.config_path = config_path
        self.proxies = {}  # {protocol: {key: config}}
        self.active_connections = {}
        self.upnp_devices = []
        self.load_config()
        self.setup_security()
        self.setup_routes()
        self.init_protocols()
        
    def load_config(self):
        try:
            with open(self.config_path, 'r') as f:
                config = json.load(f)
                self.proxies = config.get('proxies', {})
                logger.info("Configuration loaded successfully")
        except (FileNotFoundError, json.JSONDecodeError) as e:
            self.proxies = {
                ProtocolTypes.HTTP: {},
                ProtocolTypes.HTTPS: {},
                ProtocolTypes.TCP: {},
                ProtocolTypes.UDP: {},
                ProtocolTypes.STCP: {},
                ProtocolTypes.XTCP: {},
                ProtocolTypes.UPNP: {}
            }
            logger.warning(f"Using default config: {str(e)}")

    def save_config(self):
        with open(self.config_path, 'w') as f:
            json.dump({'proxies': self.proxies}, f, indent=2)
        logger.info("Configuration saved")

    def setup_security(self):
        self.api_keys = {'default': 'changeme'}  # 生产环境应从安全配置加载
        self.rate_limits = {}
        
        # 创建SSL上下文
        self.ssl_context = ssl.create_default_context(ssl.Purpose.CLIENT_AUTH)
        
        # 处理证书文件缺失的情况
        try:
            if not os.path.exists('cert.pem') or not os.path.exists('key.pem'):
                logger.warning("SSL证书文件缺失，将生成临时证书")
                self.generate_temp_cert()
            self.ssl_context.load_cert_chain('cert.pem', 'key.pem')
        except Exception as e:
            logger.error(f"无法加载SSL证书: {e}")
            # 如果HTTPS不可用，可以继续运行HTTP服务
            self.ssl_context = None 
    def generate_temp_cert(self):
        """生成临时自签名证书"""
        from cryptography import x509
        from cryptography.x509.oid import NameOID
        from cryptography.hazmat.primitives import hashes, serialization
        from cryptography.hazmat.primitives.asymmetric import rsa
        from cryptography.hazmat.backends import default_backend
        import datetime

        # 生成私钥
        key = rsa.generate_private_key(
            public_exponent=65537,
            key_size=2048,
            backend=default_backend()
        )

        # 生成自签名证书
        subject = issuer = x509.Name([
            x509.NameAttribute(NameOID.COUNTRY_NAME, "CN"),
            x509.NameAttribute(NameOID.STATE_OR_PROVINCE_NAME, "Internet"),
            x509.NameAttribute(NameOID.LOCALITY_NAME, "Internet"),
            x509.NameAttribute(NameOID.ORGANIZATION_NAME, "Tunnel Proxy"),
            x509.NameAttribute(NameOID.COMMON_NAME, "localhost"),
        ])
        
        cert = x509.CertificateBuilder().subject_name(
            subject
        ).issuer_name(
            issuer
        ).public_key(
            key.public_key()
        ).serial_number(
            x509.random_serial_number()
        ).not_valid_before(
            datetime.datetime.utcnow()
        ).not_valid_after(
            datetime.datetime.utcnow() + datetime.timedelta(days=365)
        ).add_extension(
            x509.SubjectAlternativeName([x509.DNSName("localhost")]),
            critical=False,
        ).sign(key, hashes.SHA256(), default_backend())

        # 保存私钥
        with open("key.pem", "wb") as f:
            f.write(key.private_bytes(
                encoding=serialization.Encoding.PEM,
                format=serialization.PrivateFormat.TraditionalOpenSSL,
                encryption_algorithm=serialization.NoEncryption(),
            ))

        # 保存证书
        with open("cert.pem", "wb") as f:
            f.write(cert.public_bytes(serialization.Encoding.PEM))

        logger.info("已生成临时自签名证书")
    def setup_routes(self):
        @self.app.before_request
        def authenticate():
            if request.path == '/health':
                return
            if request.path.startswith('/_xtcp/'):
                return
            api_key = request.headers.get('X-API-KEY')
            if api_key not in self.api_keys.values():
                abort(403, description="Invalid API key")

        @self.app.route('/<path:path>', methods=['GET', 'POST', 'PUT', 'DELETE', 'PATCH', 'OPTIONS'])
        def http_proxy(path):
            return self.handle_http_request(ProtocolTypes.HTTP, path)

        @self.app.route('/health')
        def health_check():
            return {
                'status': 'running',
                'protocols': {proto: len(configs) for proto, configs in self.proxies.items()},
                'active_connections': sum(len(c) for c in self.active_connections.values())
            }

        @self.app.route('/_stcp/<token>', methods=['POST'])
        def stcp_connect(token):
            return self.handle_stcp_connection(token)

        @self.app.route('/_xtcp/<session_id>', methods=['GET', 'POST'])
        def xtcp_session(session_id):
            return self.handle_xtcp_session(session_id)

    def init_protocols(self):
        # 初始化所有协议的连接
        for protocol, configs in self.proxies.items():
            for key, config in configs.items():
                try:
                    if protocol == ProtocolTypes.TCP:
                        self.start_tcp_proxy(config)
                    elif protocol == ProtocolTypes.UDP:
                        self.start_udp_proxy(config)
                    elif protocol == ProtocolTypes.UPNP:
                        self.setup_upnp_mapping(config)
                except Exception as e:
                    logger.error(f"Failed to initialize {protocol} proxy {key}: {str(e)}")

    def handle_http_request(self, protocol, path):
        """处理HTTP/HTTPS代理请求"""
        client_ip = request.remote_addr
        self.update_rate_limit(client_ip)
        
        host = request.headers.get('X-Tunnel-Target') or request.host.split(':')[0]
        if not host or host not in self.proxies[protocol]:
            abort(400, description="Invalid tunnel target")

        target_config = self.proxies[protocol][host]
        target_url = target_config['target']
        
        try:
            # 准备请求头
            headers = Headers()
            for key, value in request.headers:
                if key.lower() not in ['host', 'content-length', 'content-encoding']:
                    headers.add(key, value)

            # 添加自定义头
            if 'headers' in target_config:
                for hdr, val in target_config['headers'].items():
                    headers.add(hdr, val)

            # 转发请求
            resp = requests.request(
                method=request.method,
                url=f"{target_url.rstrip('/')}/{path.lstrip('/')}",
                headers=headers,
                data=request.get_data(),
                cookies=request.cookies,
                allow_redirects=False,
                stream=True,
                timeout=target_config.get('timeout', 30)
            )

            # 构建响应
            response_headers = Headers()
            for key, value in resp.headers.items():
                if key.lower() not in ['content-length', 'content-encoding', 'transfer-encoding']:
                    response_headers.add(key, value)

            return Response(
                resp.iter_content(chunk_size=8192),
                status=resp.status_code,
                headers=response_headers,
                direct_passthrough=True
            )
        except requests.exceptions.RequestException as e:
            logger.error(f"{protocol} proxy error: {e}")
            abort(502, description=str(e))

    def update_rate_limit(self, client_ip):
        """更新速率限制计数器"""
        now = int(time.time())
        if client_ip not in self.rate_limits:
            self.rate_limits[client_ip] = {'count': 0, 'window': now}
        
        if now - self.rate_limits[client_ip]['window'] > 60:  # 1分钟窗口
            self.rate_limits[client_ip] = {'count': 0, 'window': now}
        
        self.rate_limits[client_ip]['count'] += 1
        if self.rate_limits[client_ip]['count'] > 100:  # 限制100请求/分钟
            abort(429, description="Rate limit exceeded")

    def start_tcp_proxy(self, config):
        """启动TCP代理"""
        external_port = config['external_port']
        internal_host = config['internal_host']
        internal_port = config['internal_port']
        
        def proxy_thread():
            with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as server_socket:
                server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
                server_socket.bind(('0.0.0.0', external_port))
                server_socket.listen(5)
                server_socket.settimeout(1)
                
                logger.info(f"TCP proxy listening on port {external_port}")
                self.signals.status_update.emit(f"TCP:{external_port} 运行中")
                
                while external_port in self.proxies[ProtocolTypes.TCP]:
                    try:
                        client_socket, client_addr = server_socket.accept()
                        conn_id = f"tcp_{external_port}_{client_addr[0]}_{client_addr[1]}"
                        logger.info(f"New TCP connection from {client_addr} (ID: {conn_id})")
                        
                        self.active_connections[conn_id] = {
                            'type': ProtocolTypes.TCP,
                            'start_time': datetime.now(),
                            'client': client_addr
                        }
                        
                        threading.Thread(
                            target=self.handle_tcp_connection,
                            args=(conn_id, client_socket, internal_host, internal_port),
                            daemon=True
                        ).start()
                    except socket.timeout:
                        continue
                    except Exception as e:
                        logger.error(f"TCP accept error: {e}")
                        break

        threading.Thread(target=proxy_thread, daemon=True).start()

    def handle_tcp_connection(self, conn_id, client_socket, internal_host, internal_port):
        """处理TCP连接"""
        try:
            with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as internal_socket:
                internal_socket.settimeout(10)
                internal_socket.connect((internal_host, internal_port))
                client_socket.settimeout(10)
                
                sockets = [client_socket, internal_socket]
                while conn_id in self.active_connections:
                    readable, _, errors = select.select(sockets, [], sockets, 1)
                    
                    if errors:
                        break
                    
                    for sock in readable:
                        try:
                            data = sock.recv(4096)
                            if not data:
                                return
                            
                            if sock is client_socket:
                                internal_socket.sendall(data)
                            else:
                                client_socket.sendall(data)
                        except (socket.timeout, ConnectionResetError):
                            return
                        except Exception as e:
                            logger.error(f"TCP proxy error: {e}")
                            return
        except Exception as e:
            logger.error(f"TCP connection error: {e}")
        finally:
            client_socket.close()
            if conn_id in self.active_connections:
                del self.active_connections[conn_id]
            logger.info(f"TCP connection closed (ID: {conn_id})")

    def start_udp_proxy(self, config):
        """启动UDP代理"""
        external_port = config['external_port']
        internal_host = config['internal_host']
        internal_port = config['internal_port']
        
        def proxy_thread():
            with socket.socket(socket.AF_INET, socket.SOCK_DGRAM) as server_socket:
                server_socket.bind(('0.0.0.0', external_port))
                server_socket.settimeout(1)
                
                logger.info(f"UDP proxy listening on port {external_port}")
                self.signals.status_update.emit(f"UDP:{external_port} 运行中")
                
                while external_port in self.proxies[ProtocolTypes.UDP]:
                    try:
                        data, client_addr = server_socket.recvfrom(4096)
                        conn_id = f"udp_{external_port}_{client_addr[0]}_{client_addr[1]}"
                        
                        if conn_id not in self.active_connections:
                            self.active_connections[conn_id] = {
                                'type': ProtocolTypes.UDP,
                                'start_time': datetime.now(),
                                'client': client_addr,
                                'last_active': datetime.now()
                            }
                            logger.info(f"New UDP connection from {client_addr} (ID: {conn_id})")
                        
                        threading.Thread(
                            target=self.forward_udp_packet,
                            args=(conn_id, server_socket, client_addr, data, internal_host, internal_port),
                            daemon=True
                        ).start()
                    except socket.timeout:
                        self.cleanup_inactive_udp()
                        continue
                    except Exception as e:
                        logger.error(f"UDP proxy error: {e}")
                        break

        threading.Thread(target=proxy_thread, daemon=True).start()

    def forward_udp_packet(self, conn_id, server_socket, client_addr, data, internal_host, internal_port):
        """转发UDP数据包"""
        try:
            with socket.socket(socket.AF_INET, socket.SOCK_DGRAM) as internal_socket:
                internal_socket.sendto(data, (internal_host, internal_port))
                response, _ = internal_socket.recvfrom(4096)
                server_socket.sendto(response, client_addr)
                
                if conn_id in self.active_connections:
                    self.active_connections[conn_id]['last_active'] = datetime.now()
        except Exception as e:
            logger.error(f"UDP forwarding error: {e}")
            if conn_id in self.active_connections:
                del self.active_connections[conn_id]

    def cleanup_inactive_udp(self):
        """清理不活跃的UDP连接"""
        now = datetime.now()
        inactive = []
        
        for conn_id, conn in self.active_connections.items():
            if conn['type'] == ProtocolTypes.UDP:
                if (now - conn['last_active']).total_seconds() > 60:  # 60秒无活动
                    inactive.append(conn_id)
        
        for conn_id in inactive:
            del self.active_connections[conn_id]
            logger.info(f"Cleaned up inactive UDP connection (ID: {conn_id})")

    def handle_stcp_connection(self, token):
        """处理STCP连接"""
        if token not in self.proxies[ProtocolTypes.STCP]:
            abort(404, description="Invalid STCP token")
        
        config = self.proxies[ProtocolTypes.STCP][token]
        internal_host = config['internal_host']
        internal_port = config['internal_port']
        
        try:
            with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as internal_socket:
                internal_socket.connect((internal_host, internal_port))
                
                def generate():
                    while True:
                        data = internal_socket.recv(4096)
                        if not data:
                            break
                        yield data
                
                return Response(generate(), mimetype='application/octet-stream')
        except Exception as e:
            abort(502, description=str(e))

    def handle_xtcp_session(self, session_id):
        """处理XTCP会话"""
        if request.method == 'POST':
            # 客户端注册新会话
            token = request.headers.get('X-XTCP-Token')
            if not token or token not in self.proxies[ProtocolTypes.XTCP]:
                abort(403, description="Invalid XTCP token")
            
            config = self.proxies[ProtocolTypes.XTCP][token]
            self.active_connections[session_id] = {
                'type': ProtocolTypes.XTCP,
                'token': token,
                'start_time': datetime.now(),
                'client': request.remote_addr,
                'data': request.get_data()
            }
            
            return {'status': 'registered', 'session_id': session_id}
        else:
            # 查询会话状态
            if session_id not in self.active_connections:
                abort(404, description="Session not found")
            
            return {
                'status': 'active',
                'start_time': self.active_connections[session_id]['start_time'].isoformat(),
                'data': self.active_connections[session_id]['data'].decode('utf-8')
            }

    def setup_upnp_mapping(self, config):
        """设置UPnP端口映射"""
        try:
            devices = upnpclient.discover()
            if not devices:
                raise Exception("No UPnP devices found")
            
            self.upnp_devices = devices
            for device in devices:
                external_port = config['external_port']
                internal_port = config['internal_port']
                protocol = config.get('protocol', 'TCP')
                
                device.AddPortMapping(
                    NewRemoteHost='',
                    NewExternalPort=external_port,
                    NewProtocol=protocol,
                    NewInternalPort=internal_port,
                    NewInternalClient=config['internal_host'],
                    NewEnabled='1',
                    NewPortMappingDescription='TunnelProxy',
                    NewLeaseDuration='0'
                )
                
                logger.info(f"UPnP mapping added: {external_port}->{config['internal_host']}:{internal_port}")
                self.signals.status_update.emit(f"UPNP:{external_port} 映射成功")
        except Exception as e:
            logger.error(f"UPnP error: {e}")
            self.signals.status_update.emit(f"UPNP: 映射失败 - {str(e)}")

    def remove_upnp_mapping(self, config):
        """移除UPnP端口映射"""
        for device in self.upnp_devices:
            try:
                device.DeletePortMapping(
                    NewRemoteHost='',
                    NewExternalPort=config['external_port'],
                    NewProtocol=config.get('protocol', 'TCP')
                )
                logger.info(f"UPnP mapping removed for port {config['external_port']}")
            except Exception as e:
                logger.error(f"UPnP removal error: {e}")

    def add_proxy(self, protocol, key, config):
        """添加代理配置"""
        if protocol not in self.proxies:
            self.proxies[protocol] = {}
        
        self.proxies[protocol][key] = config
        self.save_config()
        
        try:
            if protocol == ProtocolTypes.TCP:
                self.start_tcp_proxy(config)
            elif protocol == ProtocolTypes.UDP:
                self.start_udp_proxy(config)
            elif protocol == ProtocolTypes.UPNP:
                self.setup_upnp_mapping(config)
            
            self.signals.proxy_added.emit({
                'protocol': protocol,
                'key': key,
                'config': config
            })
            return True
        except Exception as e:
            logger.error(f"Failed to start {protocol} proxy: {e}")
            if key in self.proxies[protocol]:
                del self.proxies[protocol][key]
            return False

    def remove_proxy(self, protocol, key):
        """移除代理配置"""
        if protocol not in self.proxies or key not in self.proxies[protocol]:
            return False
        
        config = self.proxies[protocol][key]
        
        # 停止相关服务
        if protocol == ProtocolTypes.UPNP:
            self.remove_upnp_mapping(config)
        
        del self.proxies[protocol][key]
        self.save_config()
        self.signals.proxy_removed.emit(protocol, key)
        return True

    def run_https_server(self):
        """运行HTTPS服务器"""
        self.app.run(host='0.0.0.0', port=443, ssl_context=self.ssl_context)

class TunnelGUI(QMainWindow):
    def __init__(self, tunnel_server):
        super().__init__()
        self.tunnel_server = tunnel_server
        self.log_buffer = []
        self.init_ui()
        self.setup_connections()
        self.setup_style()  # 新增样式设置
    def setup_style(self):
        """设置粉色二次元风格界面"""
        self.setStyleSheet("""
            QMainWindow {
                background-color: #fff0f5;
                font-family: "Microsoft YaHei";
            }
            QTabWidget::pane {
                border: 1px solid #ffb6c1;
                background: #fff0f5;
            }
            QTabBar::tab {
                background: #ffb6c1;
                color: white;
                padding: 8px;
                border-top-left-radius: 5px;
                border-top-right-radius: 5px;
            }
            QTabBar::tab:selected {
                background: #ff69b4;
            }
            QGroupBox {
                border: 2px solid #ff69b4;
                border-radius: 5px;
                margin-top: 1ex;
                font-weight: bold;
                color: #ff1493;
            }
            QGroupBox::title {
                subcontrol-origin: margin;
                left: 10px;
                padding: 0 3px;
            }
            QPushButton {
                background-color: #ffb6c1;
                border: 1px solid #ff69b4;
                border-radius: 5px;
                padding: 5px;
                color: white;
            }
            QPushButton:hover {
                background-color: #ff69b4;
            }
            QTextEdit, QLineEdit, QSpinBox, QComboBox {
                border: 1px solid #ffb6c1;
                border-radius: 3px;
                padding: 3px;
                background: white;
            }
            QLabel {
                color: #ff1493;
            }
            QStatusBar {
                background: #ffb6c1;
                color: white;
            }
        """)
    def init_ui(self):
        self.setWindowTitle('ADC映射系统 v2.0 - 如影工作室')
        self.setGeometry(100, 100, 1200, 900)
        
        # 主布局
        main_widget = QWidget()
        main_layout = QVBoxLayout()
        
        # 状态栏
        self.status_bar = self.statusBar()
        self.status_label = QLabel("就绪")
        self.status_bar.addPermanentWidget(self.status_label)
        # 添加标题栏样式
        self.setStyleSheet("""
            /* 主窗口标题栏样式 */
            QMainWindow::title {
                background-color: #ff69b4;
                color: white;
                padding: 5px;
                font-weight: bold;
                font-size: 14px;
                qproperty-alignment: AlignCenter;
            }
            
            /* 保持原有样式 */
            QMainWindow {
                background-color: #fff0f5;
                font-family: "Microsoft YaHei";
            }
            /* 其他原有样式保持不变... */
        """)
        # 标签页
        self.tab_widget = QTabWidget()
        
        # 添加原有标签页
        self.http_tab = self.create_http_tab()
        self.tcp_tab = self.create_tcp_tab()
        self.stcp_tab = self.create_stcp_tab()
        self.upnp_tab = self.create_upnp_tab()
        self.conn_tab = self.create_conn_tab()
        self.log_tab = self.create_log_tab()
        
        # 新增帮助和关于标签页
        self.help_tab = self.create_help_tab()
        self.about_tab = self.create_about_tab()
        
        self.tab_widget.addTab(self.http_tab, "HTTP/HTTPS代理")
        self.tab_widget.addTab(self.tcp_tab, "TCP/UDP代理")
        self.tab_widget.addTab(self.stcp_tab, "STCP/XTCP代理")
        self.tab_widget.addTab(self.upnp_tab, "UPNP映射")
        self.tab_widget.addTab(self.conn_tab, "连接监控")
        self.tab_widget.addTab(self.log_tab, "系统日志")
        self.tab_widget.addTab(self.help_tab, "帮助")
        self.tab_widget.addTab(self.about_tab, "关于")
        
        main_layout.addWidget(self.tab_widget)
        main_widget.setLayout(main_layout)
        self.setCentralWidget(main_widget)
        
        # 定时器
        self.log_timer = QTimer()
        self.log_timer.timeout.connect(self.update_log_display)
        self.log_timer.start(1000)
        
        self.conn_timer = QTimer()
        self.conn_timer.timeout.connect(self.update_conn_display)
        self.conn_timer.start(2000)
        
        # 启动时自动扫描UPnP设备
        self.scan_upnp_devices()
    def create_help_tab(self):
        """创建帮助标签页"""
        tab = QWidget()
        layout = QVBoxLayout()
        
        help_text = QTextEdit()
        help_text.setReadOnly(True)
        help_text.setHtml("""
            <h1 style="color:#ff1493;">ADC映射系统使用指南</h1>
            <h2 style="color:#ff69b4;">基本功能</h2>
            <p>本系统提供多种网络代理和端口映射功能：</p>
            <ul>
                <li><b>HTTP/HTTPS代理</b>：将外部域名请求转发到内部服务</li>
                <li><b>TCP/UDP代理</b>：端口转发功能</li>
                <li><b>STCP/XTCP代理</b>：安全隧道和点对点连接</li>
                <li><b>UPnP映射</b>：自动配置路由器端口映射</li>
            </ul>
            
            <h2 style="color:#ff69b4;">使用说明</h2>
            <h3>HTTP/HTTPS代理</h3>
            <p>1. 在"外部域名"输入您的域名(如example.com)</p>
            <p>2. 在"内部URL"输入您的服务地址(如http://localhost:8080)</p>
            <p>3. 点击"添加HTTP代理"按钮</p>
            
            <h3>TCP/UDP代理</h3>
            <p>1. 设置外部端口(如2222)</p>
            <p>2. 设置内部主机和端口(如localhost:22)</p>
            <p>3. 点击"添加TCP代理"按钮</p>
            
            <h3>UPnP映射</h3>
            <p>1. 点击"扫描UPnP设备"按钮</p>
            <p>2. 设置外部端口和内部主机端口</p>
            <p>3. 选择协议类型(TCP/UDP)</p>
            <p>4. 点击"添加UPnP映射"按钮</p>
            
            <h2 style="color:#ff69b4;">开发者联系方式</h2>
            <p>邮箱：support@shadow-studio.com</p>
            <p>GitHub：https://github.com/shadow-studio</p>
            <p>问题反馈：请附带详细日志信息</p>
        """)
        
        layout.addWidget(help_text)
        tab.setLayout(layout)
        return tab
    def create_about_tab(self):
        """创建关于标签页"""
        tab = QWidget()
        layout = QVBoxLayout()
        
        about_text = QTextEdit()
        about_text.setReadOnly(True)
        about_text.setHtml("""
            <div style="text-align:center;">
                <h1 style="color:#ff1493;">ADC映射系统 v2.0</h1>
                <img src="https://placeholder.pics/svg/300x200/DEDEDE/555555/如影工作室" width="300" height="200">
                <p>如影工作室出品 - 专业的网络工具开发团队</p>
                <p>Copyright © 2023 Shadow Studio. All rights reserved.</p>
                <p>本软件提供多种网络代理和端口映射功能，</p>
                <p>帮助开发者轻松实现内网穿透和端口转发。</p>
                <p>版本号：2.0.0</p>
                <p>发布日期：2023-11-15</p>
            </div>
        """)
        
        layout.addWidget(about_text)
        tab.setLayout(layout)
        return tab
    def setup_connections(self):
        """连接信号和槽"""
        self.tunnel_server.signals.log_message.connect(self.log_display.append)
        self.tunnel_server.signals.status_update.connect(self.status_label.setText)
        self.tunnel_server.signals.proxy_added.connect(self.on_proxy_added)
        self.tunnel_server.signals.proxy_removed.connect(self.on_proxy_removed)

    def create_http_tab(self):
        tab = QWidget()
        layout = QVBoxLayout()
        
        # HTTP配置组
        http_group = QGroupBox("HTTP代理配置")
        http_form = QFormLayout()
        
        self.http_external_host = QLineEdit()
        self.http_external_host.setPlaceholderText("example.com")
        http_form.addRow("外部域名:", self.http_external_host)
        
        self.http_internal_url = QLineEdit()
        self.http_internal_url.setPlaceholderText("http://localhost:8080")
        http_form.addRow("内部URL:", self.http_internal_url)
        
        self.http_custom_headers = QTextEdit()
        self.http_custom_headers.setPlaceholderText("Header1: Value1\nHeader2: Value2")
        http_form.addRow("自定义头:", self.http_custom_headers)
        
        http_btn_layout = QHBoxLayout()
        self.add_http_btn = QPushButton("添加HTTP代理")
        self.add_http_btn.clicked.connect(self.add_http_proxy)
        http_btn_layout.addWidget(self.add_http_btn)
        
        self.remove_http_btn = QPushButton("移除HTTP代理")
        self.remove_http_btn.clicked.connect(self.remove_http_proxy)
        http_btn_layout.addWidget(self.remove_http_btn)
        
        http_form.addRow(http_btn_layout)
        http_group.setLayout(http_form)
        
        # HTTPS配置组
        https_group = QGroupBox("HTTPS代理配置")
        https_form = QFormLayout()
        
        self.https_external_host = QLineEdit()
        self.https_external_host.setPlaceholderText("secure.example.com")
        https_form.addRow("外部域名:", self.https_external_host)
        
        self.https_internal_url = QLineEdit()
        self.https_internal_url.setPlaceholderText("https://localhost:8443")
        https_form.addRow("内部URL:", self.https_internal_url)
        
        self.https_verify_ssl = QCheckBox("验证SSL证书")
        self.https_verify_ssl.setChecked(True)
        https_form.addRow(self.https_verify_ssl)
        
        https_btn_layout = QHBoxLayout()
        self.add_https_btn = QPushButton("添加HTTPS代理")
        self.add_https_btn.clicked.connect(self.add_https_proxy)
        https_btn_layout.addWidget(self.add_https_btn)
        
        self.remove_https_btn = QPushButton("移除HTTPS代理")
        self.remove_https_btn.clicked.connect(self.remove_https_proxy)
        https_btn_layout.addWidget(self.remove_https_btn)
        
        https_form.addRow(https_btn_layout)
        https_group.setLayout(https_form)
        
        # 代理列表
        self.http_proxy_list = QTextEdit()
        self.http_proxy_list.setReadOnly(True)
        
        layout.addWidget(http_group)
        layout.addWidget(https_group)
        layout.addWidget(QLabel("当前HTTP/HTTPS代理:"))
        layout.addWidget(self.http_proxy_list)
        
        tab.setLayout(layout)
        return tab
    
    def create_tcp_tab(self):
        tab = QWidget()
        layout = QVBoxLayout()
        
        # TCP配置组
        tcp_group = QGroupBox("TCP代理配置")
        tcp_form = QFormLayout()
        
        self.tcp_external_port = QSpinBox()
        self.tcp_external_port.setRange(1, 65535)
        self.tcp_external_port.setValue(2222)
        tcp_form.addRow("外部端口:", self.tcp_external_port)
        
        self.tcp_internal_host = QLineEdit()
        self.tcp_internal_host.setPlaceholderText("localhost")
        tcp_form.addRow("内部主机:", self.tcp_internal_host)
        
        self.tcp_internal_port = QSpinBox()
        self.tcp_internal_port.setRange(1, 65535)
        self.tcp_internal_port.setValue(22)
        tcp_form.addRow("内部端口:", self.tcp_internal_port)
        
        tcp_btn_layout = QHBoxLayout()
        self.add_tcp_btn = QPushButton("添加TCP代理")
        self.add_tcp_btn.clicked.connect(self.add_tcp_proxy)
        tcp_btn_layout.addWidget(self.add_tcp_btn)
        
        self.remove_tcp_btn = QPushButton("移除TCP代理")
        self.remove_tcp_btn.clicked.connect(self.remove_tcp_proxy)
        tcp_btn_layout.addWidget(self.remove_tcp_btn)
        
        tcp_form.addRow(tcp_btn_layout)
        tcp_group.setLayout(tcp_form)
        
        # UDP配置组
        udp_group = QGroupBox("UDP代理配置")
        udp_form = QFormLayout()
        
        self.udp_external_port = QSpinBox()
        self.udp_external_port.setRange(1, 65535)
        self.udp_external_port.setValue(5353)
        udp_form.addRow("外部端口:", self.udp_external_port)
        
        self.udp_internal_host = QLineEdit()
        self.udp_internal_host.setPlaceholderText("localhost")
        udp_form.addRow("内部主机:", self.udp_internal_host)
        
        self.udp_internal_port = QSpinBox()
        self.udp_internal_port.setRange(1, 65535)
        self.udp_internal_port.setValue(53)
        udp_form.addRow("内部端口:", self.udp_internal_port)
        
        udp_btn_layout = QHBoxLayout()
        self.add_udp_btn = QPushButton("添加UDP代理")
        self.add_udp_btn.clicked.connect(self.add_udp_proxy)
        udp_btn_layout.addWidget(self.add_udp_btn)
        
        self.remove_udp_btn = QPushButton("移除UDP代理")
        self.remove_udp_btn.clicked.connect(self.remove_udp_proxy)
        udp_btn_layout.addWidget(self.remove_udp_btn)
        
        udp_form.addRow(udp_btn_layout)
        udp_group.setLayout(udp_form)
        
        # 代理列表
        self.tcp_proxy_list = QTextEdit()
        self.tcp_proxy_list.setReadOnly(True)
        
        layout.addWidget(tcp_group)
        layout.addWidget(udp_group)
        layout.addWidget(QLabel("当前TCP/UDP代理:"))
        layout.addWidget(self.tcp_proxy_list)
        
        tab.setLayout(layout)
        return tab
    
    def create_stcp_tab(self):
        tab = QWidget()
        layout = QVBoxLayout()
        
        # STCP配置组
        stcp_group = QGroupBox("STCP代理配置 (安全TCP)")
        stcp_form = QFormLayout()
        
        self.stcp_token = QLineEdit()
        self.stcp_token.setPlaceholderText("生成或输入安全令牌")
        stcp_form.addRow("安全令牌:", self.stcp_token)
        
        self.stcp_internal_host = QLineEdit()
        self.stcp_internal_host.setPlaceholderText("localhost")
        stcp_form.addRow("内部主机:", self.stcp_internal_host)
        
        self.stcp_internal_port = QSpinBox()
        self.stcp_internal_port.setRange(1, 65535)
        self.stcp_internal_port.setValue(3389)
        stcp_form.addRow("内部端口:", self.stcp_internal_port)
        
        stcp_btn_layout = QHBoxLayout()
        self.gen_stcp_token_btn = QPushButton("生成令牌")
        self.gen_stcp_token_btn.clicked.connect(self.generate_stcp_token)
        stcp_btn_layout.addWidget(self.gen_stcp_token_btn)
        
        self.add_stcp_btn = QPushButton("添加STCP代理")
        self.add_stcp_btn.clicked.connect(self.add_stcp_proxy)
        stcp_btn_layout.addWidget(self.add_stcp_btn)
        
        self.remove_stcp_btn = QPushButton("移除STCP代理")
        self.remove_stcp_btn.clicked.connect(self.remove_stcp_proxy)
        stcp_btn_layout.addWidget(self.remove_stcp_btn)
        
        stcp_form.addRow(stcp_btn_layout)
        stcp_group.setLayout(stcp_form)
        
        # XTCP配置组
        xtcp_group = QGroupBox("XTCP代理配置 (点对点TCP)")
        xtcp_form = QFormLayout()
        
        self.xtcp_token = QLineEdit()
        self.xtcp_token.setPlaceholderText("生成或输入安全令牌")
        xtcp_form.addRow("安全令牌:", self.xtcp_token)
        
        self.xtcp_internal_host = QLineEdit()
        self.xtcp_internal_host.setPlaceholderText("localhost")
        xtcp_form.addRow("内部主机:", self.xtcp_internal_host)
        
        self.xtcp_internal_port = QSpinBox()
        self.xtcp_internal_port.setRange(1, 65535)
        self.xtcp_internal_port.setValue(3389)
        xtcp_form.addRow("内部端口:", self.xtcp_internal_port)
        
        xtcp_btn_layout = QHBoxLayout()
        self.gen_xtcp_token_btn = QPushButton("生成令牌")
        self.gen_xtcp_token_btn.clicked.connect(self.generate_xtcp_token)
        xtcp_btn_layout.addWidget(self.gen_xtcp_token_btn)
        
        self.add_xtcp_btn = QPushButton("添加XTCP代理")
        self.add_xtcp_btn.clicked.connect(self.add_xtcp_proxy)
        xtcp_btn_layout.addWidget(self.add_xtcp_btn)
        
        self.remove_xtcp_btn = QPushButton("移除XTCP代理")
        self.remove_xtcp_btn.clicked.connect(self.remove_xtcp_proxy)
        xtcp_btn_layout.addWidget(self.remove_xtcp_btn)
        
        xtcp_form.addRow(xtcp_btn_layout)
        xtcp_group.setLayout(xtcp_form)
        
        # 代理列表
        self.stcp_proxy_list = QTextEdit()
        self.stcp_proxy_list.setReadOnly(True)
        
        layout.addWidget(stcp_group)
        layout.addWidget(xtcp_group)
        layout.addWidget(QLabel("当前STCP/XTCP代理:"))
        layout.addWidget(self.stcp_proxy_list)
        
        tab.setLayout(layout)
        return tab
    
    def create_upnp_tab(self):
        tab = QWidget()
        layout = QVBoxLayout()
        
        # UPNP配置组
        upnp_group = QGroupBox("UPNP端口映射配置")
        upnp_form = QFormLayout()
        
        self.upnp_external_port = QSpinBox()
        self.upnp_external_port.setRange(1, 65535)
        self.upnp_external_port.setValue(5432)
        upnp_form.addRow("外部端口:", self.upnp_external_port)
        
        self.upnp_internal_host = QLineEdit()
        self.upnp_internal_host.setPlaceholderText("192.168.1.100")
        upnp_form.addRow("内部主机:", self.upnp_internal_host)
        
        self.upnp_internal_port = QSpinBox()
        self.upnp_internal_port.setRange(1, 65535)
        self.upnp_internal_port.setValue(5432)
        upnp_form.addRow("内部端口:", self.upnp_internal_port)
        
        self.upnp_protocol = QComboBox()
        self.upnp_protocol.addItems(["TCP", "UDP"])
        upnp_form.addRow("协议:", self.upnp_protocol)
        
        upnp_btn_layout = QHBoxLayout()
        self.scan_upnp_btn = QPushButton("扫描UPnP设备")
        self.scan_upnp_btn.clicked.connect(self.scan_upnp_devices)
        upnp_btn_layout.addWidget(self.scan_upnp_btn)
        
        self.add_upnp_btn = QPushButton("添加UPnP映射")
        self.add_upnp_btn.clicked.connect(self.add_upnp_mapping)
        upnp_btn_layout.addWidget(self.add_upnp_btn)
        
        self.remove_upnp_btn = QPushButton("移除UPnP映射")
        self.remove_upnp_btn.clicked.connect(self.remove_upnp_mapping)
        upnp_btn_layout.addWidget(self.remove_upnp_btn)
        
        upnp_form.addRow(upnp_btn_layout)
        upnp_group.setLayout(upnp_form)
        
        # 设备列表
        self.upnp_device_list = QTextEdit()
        self.upnp_device_list.setReadOnly(True)
        
        # 映射列表
        self.upnp_mapping_list = QTextEdit()
        self.upnp_mapping_list.setReadOnly(True)
        
        layout.addWidget(upnp_group)
        layout.addWidget(QLabel("发现的UPnP设备:"))
        layout.addWidget(self.upnp_device_list)
        layout.addWidget(QLabel("当前UPnP映射:"))
        layout.addWidget(self.upnp_mapping_list)
        
        tab.setLayout(layout)
        return tab
    
    def create_conn_tab(self):
        tab = QWidget()
        layout = QVBoxLayout()
        
        # 连接列表
        self.conn_table = QTextEdit()
        self.conn_table.setReadOnly(True)
        self.conn_table.setStyleSheet("font-family: monospace;")
        
        # 控制按钮
        btn_layout = QHBoxLayout()
        self.refresh_conn_btn = QPushButton("刷新列表")
        self.refresh_conn_btn.clicked.connect(self.update_conn_display)
        btn_layout.addWidget(self.refresh_conn_btn)
        
        self.disconnect_btn = QPushButton("断开选中")
        self.disconnect_btn.clicked.connect(self.disconnect_selected)
        btn_layout.addWidget(self.disconnect_btn)
        
        layout.addLayout(btn_layout)
        layout.addWidget(self.conn_table)
        
        tab.setLayout(layout)
        return tab
    
    def create_log_tab(self):
        tab = QWidget()
        layout = QVBoxLayout()
        
        self.log_display = QTextEdit()
        self.log_display.setReadOnly(True)
        self.log_display.setStyleSheet("font-family: monospace;")
        
        # 日志控制
        control_layout = QHBoxLayout()
        self.clear_log_btn = QPushButton("清空日志")
        self.clear_log_btn.clicked.connect(self.clear_logs)
        control_layout.addWidget(self.clear_log_btn)
        
        self.log_level = QComboBox()
        self.log_level.addItems(["DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL"])
        self.log_level.setCurrentText("INFO")
        self.log_level.currentTextChanged.connect(self.change_log_level)
        control_layout.addWidget(QLabel("日志级别:"))
        control_layout.addWidget(self.log_level)
        
        self.export_log_btn = QPushButton("导出日志")
        self.export_log_btn.clicked.connect(self.export_logs)
        control_layout.addWidget(self.export_log_btn)
        
        layout.addLayout(control_layout)
        layout.addWidget(self.log_display)
        
        tab.setLayout(layout)
        return tab
    
    # 代理操作方法
    def add_http_proxy(self):
        external_host = self.http_external_host.text().strip()
        internal_url = self.http_internal_url.text().strip()
        
        if not external_host or not internal_url:
            QMessageBox.warning(self, "错误", "外部域名和内部URL不能为空")
            return
        
        try:
            headers = {}
            for line in self.http_custom_headers.toPlainText().split('\n'):
                if ':' in line:
                    key, val = line.split(':', 1)
                    headers[key.strip()] = val.strip()
            
            config = {
                'target': internal_url,
                'headers': headers,
                'timeout': 30
            }
            
            if self.tunnel_server.add_proxy(ProtocolTypes.HTTP, external_host, config):
                QMessageBox.information(self, "成功", f"已添加HTTP代理: {external_host}")
                self.update_proxy_lists()
        except Exception as e:
            QMessageBox.critical(self, "错误", f"添加HTTP代理失败: {str(e)}")
    
    def remove_http_proxy(self):
        external_host = self.http_external_host.text().strip()
        
        if not external_host:
            QMessageBox.warning(self, "错误", "请输入要移除的外部域名")
            return
        
        if self.tunnel_server.remove_proxy(ProtocolTypes.HTTP, external_host):
            QMessageBox.information(self, "成功", f"已移除HTTP代理: {external_host}")
            self.update_proxy_lists()
        else:
            QMessageBox.warning(self, "错误", f"找不到HTTP代理: {external_host}")
    
    def add_https_proxy(self):
        external_host = self.https_external_host.text().strip()
        internal_url = self.https_internal_url.text().strip()
        
        if not external_host or not internal_url:
            QMessageBox.warning(self, "错误", "外部域名和内部URL不能为空")
            return
        
        try:
            config = {
                'target': internal_url,
                'verify_ssl': self.https_verify_ssl.isChecked(),
                'timeout': 30
            }
            
            if self.tunnel_server.add_proxy(ProtocolTypes.HTTPS, external_host, config):
                QMessageBox.information(self, "成功", f"已添加HTTPS代理: {external_host}")
                self.update_proxy_lists()
        except Exception as e:
            QMessageBox.critical(self, "错误", f"添加HTTPS代理失败: {str(e)}")
    
    def remove_https_proxy(self):
        external_host = self.https_external_host.text().strip()
        
        if not external_host:
            QMessageBox.warning(self, "错误", "请输入要移除的外部域名")
            return
        
        if self.tunnel_server.remove_proxy(ProtocolTypes.HTTPS, external_host):
            QMessageBox.information(self, "成功", f"已移除HTTPS代理: {external_host}")
            self.update_proxy_lists()
        else:
            QMessageBox.warning(self, "错误", f"找不到HTTPS代理: {external_host}")
    
    def add_tcp_proxy(self):
        external_port = self.tcp_external_port.value()
        internal_host = self.tcp_internal_host.text().strip()
        internal_port = self.tcp_internal_port.value()
        
        if not internal_host:
            QMessageBox.warning(self, "错误", "内部主机不能为空")
            return
        
        try:
            config = {
                'external_port': external_port,
                'internal_host': internal_host,
                'internal_port': internal_port
            }
            
            if self.tunnel_server.add_proxy(ProtocolTypes.TCP, str(external_port), config):
                QMessageBox.information(self, "成功", 
                    f"已添加TCP代理: {external_port} -> {internal_host}:{internal_port}")
                self.update_proxy_lists()
        except Exception as e:
            QMessageBox.critical(self, "错误", f"添加TCP代理失败: {str(e)}")
    
    def remove_tcp_proxy(self):
        external_port = self.tcp_external_port.value()
        
        if self.tunnel_server.remove_proxy(ProtocolTypes.TCP, str(external_port)):
            QMessageBox.information(self, "成功", f"已移除TCP代理: {external_port}")
            self.update_proxy_lists()
        else:
            QMessageBox.warning(self, "错误", f"找不到TCP代理: {external_port}")
    
    def add_udp_proxy(self):
        external_port = self.udp_external_port.value()
        internal_host = self.udp_internal_host.text().strip()
        internal_port = self.udp_internal_port.value()
        
        if not internal_host:
            QMessageBox.warning(self, "错误", "内部主机不能为空")
            return
        
        try:
            config = {
                'external_port': external_port,
                'internal_host': internal_host,
                'internal_port': internal_port
            }
            
            if self.tunnel_server.add_proxy(ProtocolTypes.UDP, str(external_port), config):
                QMessageBox.information(self, "成功", 
                    f"已添加UDP代理: {external_port} -> {internal_host}:{internal_port}")
                self.update_proxy_lists()
        except Exception as e:
            QMessageBox.critical(self, "错误", f"添加UDP代理失败: {str(e)}")
    
    def remove_udp_proxy(self):
        external_port = self.udp_external_port.value()
        
        if self.tunnel_server.remove_proxy(ProtocolTypes.UDP, str(external_port)):
            QMessageBox.information(self, "成功", f"已移除UDP代理: {external_port}")
            self.update_proxy_lists()
        else:
            QMessageBox.warning(self, "错误", f"找不到UDP代理: {external_port}")
    
    def generate_stcp_token(self):
        import secrets
        token = secrets.token_urlsafe(16)
        self.stcp_token.setText(token)
    
    def add_stcp_proxy(self):
        token = self.stcp_token.text().strip()
        internal_host = self.stcp_internal_host.text().strip()
        internal_port = self.stcp_internal_port.value()
        
        if not token or not internal_host:
            QMessageBox.warning(self, "错误", "令牌和内部主机不能为空")
            return
        
        try:
            config = {
                'internal_host': internal_host,
                'internal_port': internal_port
            }
            
            if self.tunnel_server.add_proxy(ProtocolTypes.STCP, token, config):
                QMessageBox.information(self, "成功", 
                    f"已添加STCP代理: {token} -> {internal_host}:{internal_port}")
                self.update_proxy_lists()
        except Exception as e:
            QMessageBox.critical(self, "错误", f"添加STCP代理失败: {str(e)}")
    
    def remove_stcp_proxy(self):
        token = self.stcp_token.text().strip()
        
        if not token:
            QMessageBox.warning(self, "错误", "请输入要移除的令牌")
            return
        
        if self.tunnel_server.remove_proxy(ProtocolTypes.STCP, token):
            QMessageBox.information(self, "成功", f"已移除STCP代理: {token}")
            self.update_proxy_lists()
        else:
            QMessageBox.warning(self, "错误", f"找不到STCP代理: {token}")
    
    def generate_xtcp_token(self):
        import secrets
        token = secrets.token_urlsafe(16)
        self.xtcp_token.setText(token)
    
    def add_xtcp_proxy(self):
        token = self.xtcp_token.text().strip()
        internal_host = self.xtcp_internal_host.text().strip()
        internal_port = self.xtcp_internal_port.value()
        
        if not token or not internal_host:
            QMessageBox.warning(self, "错误", "令牌和内部主机不能为空")
            return
        
        try:
            config = {
                'internal_host': internal_host,
                'internal_port': internal_port
            }
            
            if self.tunnel_server.add_proxy(ProtocolTypes.XTCP, token, config):
                QMessageBox.information(self, "成功", 
                    f"已添加XTCP代理: {token} -> {internal_host}:{internal_port}")
                self.update_proxy_lists()
        except Exception as e:
            QMessageBox.critical(self, "错误", f"添加XTCP代理失败: {str(e)}")
    
    def remove_xtcp_proxy(self):
        token = self.xtcp_token.text().strip()
        
        if not token:
            QMessageBox.warning(self, "错误", "请输入要移除的令牌")
            return
        
        if self.tunnel_server.remove_proxy(ProtocolTypes.XTCP, token):
            QMessageBox.information(self, "成功", f"已移除XTCP代理: {token}")
            self.update_proxy_lists()
        else:
            QMessageBox.warning(self, "错误", f"找不到XTCP代理: {token}")
    
    def scan_upnp_devices(self):
        """扫描UPnP设备并自动检测支持情况"""
        try:
            self.upnp_device_list.clear()
            
            # 显示扫描中状态
            self.upnp_device_list.append("正在扫描UPnP设备...")
            QApplication.processEvents()  # 更新UI
            
            devices = upnpclient.discover()
            self.tunnel_server.upnp_devices = devices
            
            if not devices:
                self.upnp_device_list.append("未发现UPnP设备")
                self.upnp_device_list.append("提示：请确保您的路由器支持UPnP并且已启用")
                return
                
            self.upnp_device_list.clear()
            
            # 检查每个设备是否支持端口映射
            supported_devices = 0
            for i, device in enumerate(devices, 1):
                try:
                    # 测试设备是否支持端口映射
                    device.WANIPConn1.GetStatusInfo()
                    supported = "✓ 支持端口映射"
                    supported_devices += 1
                except:
                    supported = "✗ 不支持端口映射"
                
                self.upnp_device_list.append(
                    f"{i}. {device.friendly_name} ({device.model_description})")
                self.upnp_device_list.append(f"   → {device.model_name} @ {device.location}")
                self.upnp_device_list.append(f"   → {supported}\n")
            
            # 显示扫描结果摘要
            summary = QLabel()
            summary.setText(f"<b>扫描完成！发现 {len(devices)} 个UPnP设备，其中 {supported_devices} 个支持端口映射</b>")
            summary.setStyleSheet("color: #ff1493;")
            
            # 将摘要插入到设备列表上方
            layout = self.upnp_tab.layout()
            if layout is not None:
                layout.insertWidget(layout.indexOf(self.upnp_device_list), summary)
            
            if supported_devices > 0:
                QMessageBox.information(self, "成功", 
                    f"发现 {supported_devices} 个支持端口映射的UPnP设备")
            else:
                QMessageBox.warning(self, "警告", 
                    "未发现支持端口映射的UPnP设备，请检查路由器设置")
                    
        except Exception as e:
            self.upnp_device_list.append(f"扫描失败: {str(e)}")
            QMessageBox.critical(self, "错误", f"扫描UPnP设备失败: {str(e)}")
    
    def add_upnp_mapping(self):
        external_port = self.upnp_external_port.value()
        internal_host = self.upnp_internal_host.text().strip()
        internal_port = self.upnp_internal_port.value()
        protocol = self.upnp_protocol.currentText()
        
        if not internal_host:
            QMessageBox.warning(self, "错误", "内部主机不能为空")
            return
        
        try:
            config = {
                'external_port': external_port,
                'internal_host': internal_host,
                'internal_port': internal_port,
                'protocol': protocol
            }
            
            if self.tunnel_server.add_proxy(ProtocolTypes.UPNP, f"{protocol}:{external_port}", config):
                QMessageBox.information(self, "成功", 
                    f"已添加UPnP映射: {external_port} -> {internal_host}:{internal_port} ({protocol})")
                self.update_proxy_lists()
        except Exception as e:
            QMessageBox.critical(self, "错误", f"添加UPnP映射失败: {str(e)}")
    
    def remove_upnp_mapping(self):
        external_port = self.upnp_external_port.value()
        protocol = self.upnp_protocol.currentText()
        key = f"{protocol}:{external_port}"
        
        if self.tunnel_server.remove_proxy(ProtocolTypes.UPNP, key):
            QMessageBox.information(self, "成功", f"已移除UPnP映射: {external_port} ({protocol})")
            self.update_proxy_lists()
        else:
            QMessageBox.warning(self, "错误", f"找不到UPnP映射: {external_port} ({protocol})")
    
    # 更新显示方法
    def update_proxy_lists(self):
        """更新所有代理列表显示"""
        # HTTP/HTTPS代理
        self.http_proxy_list.clear()
        for proto in [ProtocolTypes.HTTP, ProtocolTypes.HTTPS]:
            for host, config in self.tunnel_server.proxies.get(proto, {}).items():
                self.http_proxy_list.append(f"[{proto}] {host} -> {config['target']}")
        
        # TCP/UDP代理
        self.tcp_proxy_list.clear()
        for proto in [ProtocolTypes.TCP, ProtocolTypes.UDP]:
            for port, config in self.tunnel_server.proxies.get(proto, {}).items():
                self.tcp_proxy_list.append(
                    f"[{proto}] {port} -> {config['internal_host']}:{config['internal_port']}")
        
        # STCP/XTCP代理
        self.stcp_proxy_list.clear()
        for proto in [ProtocolTypes.STCP, ProtocolTypes.XTCP]:
            for token, config in self.tunnel_server.proxies.get(proto, {}).items():
                self.stcp_proxy_list.append(
                    f"[{proto}] {token} -> {config['internal_host']}:{config['internal_port']}")
        
        # UPNP映射
        self.upnp_mapping_list.clear()
        for key, config in self.tunnel_server.proxies.get(ProtocolTypes.UPNP, {}).items():
            self.upnp_mapping_list.append(
                f"[UPNP] {config['external_port']} ({config.get('protocol', 'TCP')}) -> "
                f"{config['internal_host']}:{config['internal_port']}")
    
    def update_conn_display(self):
        """更新连接监控显示"""
        self.conn_table.clear()
        if not self.tunnel_server.active_connections:
            self.conn_table.append("没有活跃连接")
            return
        
        self.conn_table.append("ID\t类型\t客户端\t持续时间\t状态")
        self.conn_table.append("-" * 80)
        
        for conn_id, conn in self.tunnel_server.active_connections.items():
            duration = datetime.now() - conn['start_time']
            self.conn_table.append(
                f"{conn_id[:16]}...\t{conn['type']}\t"
                f"{conn['client']}\t{duration}\t活跃")
    
    def update_log_display(self):
        """更新日志显示"""
        try:
            with open('tunnel.log', 'r', encoding='utf-8') as f:
                logs = f.readlines()
            
            new_logs = [log for log in logs if log not in self.log_buffer]
            if new_logs:
                self.log_display.append(''.join(new_logs))
                self.log_buffer.extend(new_logs)
                
                # 保持缓冲区大小合理
                if len(self.log_buffer) > 1000:
                    self.log_buffer = self.log_buffer[-500:]
                
                # 自动滚动到底部
                scrollbar = self.log_display.verticalScrollBar()
                scrollbar.setValue(scrollbar.maximum())
        except FileNotFoundError:
            pass
    
    def clear_logs(self):
        """清空日志"""
        self.log_display.clear()
        self.log_buffer = []
        try:
            open('tunnel.log', 'w').close()
        except:
            pass
    
    def export_logs(self):
        """导出日志到文件"""
        from PyQt5.QtWidgets import QFileDialog
        file_name, _ = QFileDialog.getSaveFileName(
            self, "导出日志", "", "日志文件 (*.log);;所有文件 (*)")
        
        if file_name:
            try:
                with open('tunnel.log', 'r') as src, open(file_name, 'w') as dst:
                    dst.writelines(src.readlines())
                QMessageBox.information(self, "成功", f"日志已导出到 {file_name}")
            except Exception as e:
                QMessageBox.critical(self, "错误", f"导出日志失败: {str(e)}")
    
    def change_log_level(self, level):
        """更改日志级别"""
        logger.setLevel(level)
        for handler in logger.handlers:
            handler.setLevel(level)
    
    def disconnect_selected(self):
        """断开选中的连接"""
        try:
            # 获取当前选中的文本
            cursor = self.conn_table.textCursor()
            selected_text = cursor.selectedText().strip()
            
            if not selected_text:
                QMessageBox.warning(self, "警告", "请先选择要断开的连接")
                return
                
            # 解析连接ID
            conn_id = None
            for line in selected_text.split('\n'):
                if '...' in line:  # 匹配ID格式
                    conn_id = line.split('...')[0].strip()
                    break
                    
            if not conn_id:
                QMessageBox.warning(self, "警告", "无法识别连接ID")
                return
                
            # 查找完整连接ID
            full_conn_id = None
            for key in self.tunnel_server.active_connections.keys():
                if key.startswith(conn_id):
                    full_conn_id = key
                    break
                    
            if not full_conn_id:
                QMessageBox.warning(self, "警告", "找不到对应的连接")
                return
                
            # 根据连接类型执行断开操作
            conn_info = self.tunnel_server.active_connections[full_conn_id]
            
            if conn_info['type'] in [ProtocolTypes.TCP, ProtocolTypes.STCP]:
                # 对于TCP类连接，关闭socket
                for thread in threading.enumerate():
                    if hasattr(thread, 'conn_id') and thread.conn_id == full_conn_id:
                        if hasattr(thread, 'client_socket'):
                            try:
                                thread.client_socket.close()
                            except:
                                pass
                
            elif conn_info['type'] == ProtocolTypes.UDP:
                # 对于UDP连接，直接从活跃连接中移除
                pass
                
            # 从活跃连接中移除
            if full_conn_id in self.tunnel_server.active_connections:
                del self.tunnel_server.active_connections[full_conn_id]
                
            QMessageBox.information(self, "成功", f"已断开连接: {full_conn_id}")
            self.update_conn_display()
            
        except Exception as e:
            QMessageBox.critical(self, "错误", f"断开连接失败: {str(e)}")
    
    def on_proxy_added(self, proxy_info):
        """处理代理添加信号"""
        protocol = proxy_info['protocol']
        key = proxy_info['key']
        config = proxy_info['config']
        
        if protocol == ProtocolTypes.HTTP or protocol == ProtocolTypes.HTTPS:
            text = f"[{protocol}] {key} -> {config['target']}"
            self.http_proxy_list.append(text)
        elif protocol == ProtocolTypes.TCP or protocol == ProtocolTypes.UDP:
            text = f"[{protocol}] {key} -> {config['internal_host']}:{config['internal_port']}"
            self.tcp_proxy_list.append(text)
        elif protocol == ProtocolTypes.STCP or protocol == ProtocolTypes.XTCP:
            text = f"[{protocol}] {key} -> {config['internal_host']}:{config['internal_port']}"
            self.stcp_proxy_list.append(text)
        elif protocol == ProtocolTypes.UPNP:
            text = (f"[UPNP] {config['external_port']} ({config.get('protocol', 'TCP')}) -> "
                   f"{config['internal_host']}:{config['internal_port']}")
            self.upnp_mapping_list.append(text)
    
    def on_proxy_removed(self, protocol, key):
        """处理代理移除信号"""
        self.update_proxy_lists()

def run_flask_app(tunnel_server):
    tunnel_server.app.run(host='0.0.0.0', port=5000)

def run_https_server(tunnel_server):
    tunnel_server.app.run(host='0.0.0.0', port=443, ssl_context=tunnel_server.ssl_context)

if __name__ == '__main__':
    # 确保必要的文件存在
    if not os.path.exists('config.json'):
        with open('config.json', 'w') as f:
            json.dump({
                'proxies': {
                    'HTTP': {},
                    'HTTPS': {},
                    'TCP': {},
                    'UDP': {},
                    'STCP': {},
                    'XTCP': {},
                    'UPNP': {}
                }
            }, f)
        logger.info("已创建默认配置文件")

    # 创建隧道服务器
    tunnel_server = TunnelServer()
    
    # 启动HTTP服务
    flask_thread = threading.Thread(
        target=run_flask_app,
        args=(tunnel_server,),
        daemon=True
    )
    flask_thread.start()
    
    # 如果SSL证书可用，启动HTTPS服务
    if tunnel_server.ssl_context:
        https_thread = threading.Thread(
            target=run_https_server,
            args=(tunnel_server,),
            daemon=True
        )
        https_thread.start()
    else:
        logger.warning("HTTPS服务不可用，仅运行HTTP服务")
    
    # 启动GUI
    app = QApplication(sys.argv)
    gui = TunnelGUI(tunnel_server)
    gui.show()
    
    sys.exit(app.exec_())